package com.hzgj.bcl.soa.net;

import com.hzgj.bcl.soa.interceptor.Compress;
import com.hzgj.bcl.soa.rpc.exception.RpcException;
import com.hzgj.bcl.soa.rpc.message.Message;
import com.hzgj.bcl.soa.util.Bytes;
import com.hzgj.bcl.soa.util.Constants;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;

/**
 * Created by dell on 2015/3/31.
 * <p>
 * header(16) {magic(2),{serializationId(4),compress(1),event(1),blank(1),request/response(1)}(1)
 * status(1),sId(8),datalength(4)}
 */

public class DefaultEncoder extends Encoder {

    protected static final int SERIALIZATION_MASK = 0xf;
    // header length.
    static final int HEADER_LENGTH = 16;
    // magic header.
    static final short MAGIC = (short) 0xdadb;
    // message flag.
    static final byte FLAG_REQUEST = (byte) 0x80;
    static final byte FLAG_EVENT = (byte) 0x20;
    static final byte FLAG_COMPRESS = (byte) 0x10;
    private static Logger logger = LoggerFactory.getLogger(DefaultEncoder.class);

    public static void checkPayload(Channel channel, long size) throws IOException {
        int payload = Constants.DEFAULT_PAYLOAD;
        if (payload > 0 && size > payload) {
            IOException e = new IOException("Data length too large: " + size + ", max payload: " + payload + ", channel: " + channel);
            logger.error("{}", e);
            throw e;
        }
    }

    @Override
    protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {

        Byte serializationId = msg.getSerializationId();
        long sId = (long) msg.getHeader(Constants.MESSAGE_SID);
        Integer type = (Integer) msg.getHeader(Constants.MESSAGE_TYPE);
        try {
            // header.
            byte[] header = new byte[HEADER_LENGTH];
            // set magic number.
            Bytes.short2bytes(MAGIC, header);

            switch (type) {
                case Message.EXC_RSP:
                    header[3] = 1;
                    header[2] = serializationId;
                    break;
                case Message.REQ:
                    header[3] = 0;
                    header[2] = (byte) (FLAG_REQUEST | serializationId);
                    break;
                case Message.RSP:
                    header[3] = 0;
                    header[2] = serializationId;
                    break;
            }

            boolean isEvent = msg.getHeader(Constants.MESSAGE_HEADER_EVENT) != null;
            Byte compress = ((Byte) msg.getHeader(Compress.MESSAGE_HEADER_COMPRESS));
            boolean isCompress = (compress != null && compress == (byte) 1);

            if (isEvent) {
                header[2] |= FLAG_EVENT;
            }
            if (isCompress) {
                header[2] |= FLAG_COMPRESS;
            }

            // set request id.
            Bytes.long2bytes(sId, header, 4);

            // encode request data.
            int savedWriteIndex = out.writerIndex();
            out.writerIndex(savedWriteIndex + HEADER_LENGTH);
            out.writeBytes((byte[]) msg.getPayload());
            int len = msg.getLength();
            checkPayload(ctx.channel(), len);
            Bytes.int2bytes(len, header, 12);

            out.writerIndex(savedWriteIndex);
            // write header.
            out.writeBytes(header);
            out.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
        } catch (Exception e) {
            if (Message.REQ != (Integer) msg.getHeader(Constants.MESSAGE_TYPE)) {
                logger.warn("Fail to encode response: {}, handle bad_response info instead, cause: {}" + msg, e.getMessage());
                Message message = new Message();
                message.putHeader(Constants.MESSAGE_SID, sId)
                        .putHeader(Constants.MESSAGE_HEADER_SERIALIZATION_ID, serializationId)
                        .setPayload(new RpcException("Failed to handle response: " + msg + ", cause: ", e));
                ctx.channel().writeAndFlush(message);
                return;
            }
            throw e;
        }
    }

    @Override
    public String getName() {
        return "default";
    }
}
